{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction to the pandapower control module"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial introduces the pandapower controle module with the example of tap changer control. For this, we first load the MV oberrhein network that contains two 110/20 kV transformers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:50.311864Z",
     "start_time": "2025-10-17T08:34:48.952893Z"
    }
   },
   "outputs": [],
   "source": [
    "# Importing necessary packages\n",
    "from pandapower.run import runpp\n",
    "from pandapower.networks import mv_oberrhein\n",
    "\n",
    "net = mv_oberrhein()\n",
    "net.trafo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we run a power flow, we can see the voltage at the low voltage side of the transformers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:50.551487Z",
     "start_time": "2025-10-17T08:34:50.455229Z"
    }
   },
   "outputs": [],
   "source": [
    "runpp(net)\n",
    "net.res_trafo.vm_lv_pu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Both transformers include a tap changer with a range of -9 to +9, which are set to positions -2 and -3 respectively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:51.162535Z",
     "start_time": "2025-10-17T08:34:51.142442Z"
    }
   },
   "outputs": [],
   "source": [
    "net.trafo[\"tap_pos\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The tap position is constant within a power flow calculation. A controller can now be used to control the tap changer position depending on the bus voltage."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Discrete Tap Control"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The DiscreteTapControl from the pandapower control package receives a deadband of permissable voltage and uses the tap changer to keep the voltage within this voltage band. We define such a controller for the first transformer in the oberrhein network with a deadband of 0.99 to 1.01pu:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:51.362201Z",
     "start_time": "2025-10-17T08:34:51.338390Z"
    }
   },
   "outputs": [],
   "source": [
    "import pandapower.control as control\n",
    "trafo_controller = control.DiscreteTapControl(net=net, element_index=114, vm_lower_pu=0.99, vm_upper_pu=1.01)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The initiated controller automatically registers in the net. It can be found in the controller table:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:51.596448Z",
     "start_time": "2025-10-17T08:34:51.572552Z"
    }
   },
   "outputs": [],
   "source": [
    "net.controller"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We now run a controlled power flow by setting **run_control=True** within the runpp arguments and check the transformer voltage:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:52.229302Z",
     "start_time": "2025-10-17T08:34:51.829048Z"
    }
   },
   "outputs": [],
   "source": [
    "# running a control-loop\n",
    "runpp(net, run_control=True)\n",
    "net.res_trafo.vm_lv_pu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The voltage at transformer 114 is now within the given range. If we checke the transformer table, we can see that the tap position of the first transformer as been changed from -2 to -1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:53.346901Z",
     "start_time": "2025-10-17T08:34:53.326986Z"
    }
   },
   "outputs": [],
   "source": [
    "net.trafo[\"tap_pos\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:53.872107Z",
     "start_time": "2025-10-17T08:34:53.853753Z"
    }
   },
   "outputs": [],
   "source": [
    "net.trafo.std_type"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Discrete Tap Control: Alternative mode"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:54.561462Z",
     "start_time": "2025-10-17T08:34:54.541286Z"
    }
   },
   "outputs": [],
   "source": [
    "net.controller.drop([trafo_controller.index], inplace = True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The tap step percent is an alternative way to Discrete Tap Control. It sets a single voltage instead of a deadband of permissable voltage and calculates the voltage limit for the upper and lower side by adding and subtracting the vm_delta_pu respectively. We define such a controller for the first transformer as an alternative to Discrete Tap Control in the oberrhein network with a voltage of 1.05 pu:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:54.939439Z",
     "start_time": "2025-10-17T08:34:54.911226Z"
    }
   },
   "outputs": [],
   "source": [
    "vm_delta_pu = net.trafo.at[142, \"tap_step_percent\"] / 100. * .5\n",
    "vm_lower_pu = 1.05 - vm_delta_pu\n",
    "vm_upper_pu = 1.05 + vm_delta_pu\n",
    "print(vm_delta_pu,vm_lower_pu,vm_upper_pu)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We got the limit of upper side voltage by adding the vm_delta_pu to the set voltage and the lower side voltage limit by substracting the vm_delta_pu from the vm_set_pu."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:55.267965Z",
     "start_time": "2025-10-17T08:34:55.247229Z"
    }
   },
   "outputs": [],
   "source": [
    "trafo_controller2 = control.DiscreteTapControl.from_tap_step_percent(\n",
    "    net=net, element_index=114, vm_set_pu=1.05, side=\"lv\", tol=1e-3, in_service=True, order=0,\n",
    "    drop_same_existing_ctrl=False, matching_params=None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we added the tap step percent to the first transformer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:55.747109Z",
     "start_time": "2025-10-17T08:34:55.406983Z"
    }
   },
   "outputs": [],
   "source": [
    "runpp(net, run_control=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:55.906189Z",
     "start_time": "2025-10-17T08:34:55.867655Z"
    }
   },
   "outputs": [],
   "source": [
    "net.controller"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:56.110935Z",
     "start_time": "2025-10-17T08:34:56.096368Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_trafo.vm_lv_pu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The voltage at transformer 114 is now changed to 1.048740 which is between the upper and lower limits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:56.595416Z",
     "start_time": "2025-10-17T08:34:56.568322Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_trafo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:56.979038Z",
     "start_time": "2025-10-17T08:34:56.968625Z"
    }
   },
   "outputs": [],
   "source": [
    "net.trafo[\"tap_pos\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The tap position of the second transformer has been changed from -1 to -4:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Continous Tap Control\n",
    "\n",
    "It is also possible to control transformer with a **ContiniousTapControl** strategy. Instead of a range, this type of controller is able to achieve an exact output voltage. For this it assumes tap positions as floating numbers. We define such a controller for the second transformer in the network:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:57.598919Z",
     "start_time": "2025-10-17T08:34:57.582712Z"
    }
   },
   "outputs": [],
   "source": [
    "trafo_controller = control.ContinuousTapControl(net=net, element_index=142, vm_set_pu=0.98, tol=1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we now run the result, the low voltage side of the second transformer is controlled to exactly 0.98 pu:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:58.332576Z",
     "start_time": "2025-10-17T08:34:57.816518Z"
    }
   },
   "outputs": [],
   "source": [
    "runpp(net, run_control=True)\n",
    "net.res_trafo.vm_lv_pu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The tap position is set to -0.07:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-17T08:34:58.483905Z",
     "start_time": "2025-10-17T08:34:58.469847Z"
    }
   },
   "outputs": [],
   "source": [
    "net.trafo[\"tap_pos\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While this obviously would not possible in real transformers, it can be useful to assume continous taps in large scale studies to avoid big steps in the results."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
